-
Notifications
You must be signed in to change notification settings - Fork 8
feat(upgrade): add verification release is deployable #3136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(upgrade): add verification release is deployable #3136
Conversation
|
This PR has been released (on staging) and is available for download with a embedded-cluster-smoke-test-staging-app license ID. Online Installer: Airgap Installer (may take a few minutes before the airgap bundle is built): Happy debugging! |
5e75ed0 to
e21fcad
Compare
pkg-new/validation/upgradable.go
Outdated
| if len(requiredReleases) > 0 { | ||
| // Extract version labels from required releases | ||
| for _, release := range requiredReleases { | ||
| requiredVersions = append(requiredVersions, release.VersionLabel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why dont you check TargetAppSequence here like you do for online?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong yes, I'm trying to figure out how the required versions are sorted and what gets added to the metadata.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed it in the latest changes.
| } | ||
|
|
||
| // Check if minor version is being skipped | ||
| if targetK8s.Minor() > currentK8s.Minor()+1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you also need to check for downgrade here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not doing it because we're also checking for EC version downgrades already (and block it altogether) so that code path would never be used. We could do it if we want to ensure correctness though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I completely forgot about the fact you could theoretically still move to a higher EC version but a lower kube version. You're right, sorry I missed it @emosbaugh. Fixed via - 959f483
| } | ||
|
|
||
| // GetPendingReleases fetches pending releases from the Replicated API | ||
| func (c *client) GetPendingReleases(ctx context.Context, channelID string, currentSequence int64, opts *PendingReleasesOptions) (*PendingReleasesResponse, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it worth adding reporting info to this call? i know we can add the app status, sequence, number of nodes, etc...
it could be done in a follow up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question... Idk 🤷 who would be the best person to talk about this reporting info, its use and what's expected?
We have other instances in the cli where we hit this endpoint without sending any data whatsoever -
embedded-cluster/cmd/installer/cli/release.go
Lines 29 to 36 in 030261b
| func getCurrentAppChannelRelease(ctx context.Context, license *kotsv1beta1.License, channelID string) (*apiChannelRelease, error) { | |
| query := url.Values{} | |
| query.Set("selectedChannelId", channelID) | |
| query.Set("channelSequence", "") // sending an empty string will return the latest channel release | |
| query.Set("isSemverSupported", "true") | |
| apiURL := replicatedAppURL() | |
| url := fmt.Sprintf("%s/release/%s/pending?%s", apiURL, license.Spec.AppSlug, query.Encode()) |
| } | ||
| upgradeConfig.license = updatedLicense | ||
| upgradeConfig.licenseBytes = licenseBytes | ||
| upgradeConfig.replicatedAPIClient = replicatedAPI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be taken out of the conditional as it is only set in airgap mode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the other way around, it's only set in online mode right? And isn't that what we want? In airgap there's no point in initialising the replicated API client right?
0f083d4 to
2fabba8
Compare
| } | ||
|
|
||
| // Perform validation | ||
| if err := validation.ValidateIsReleaseUpgradable(ctx, opts); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the logic that builds these opts should also be put into its own "hop" (sub-function) and unit tested IMO because there's an external dependency at the end of this path. note: the installation object should be passed to it though so it doesn't interact with the cluster or need a fake k8s client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sgalsaleh, we cannot unit test this method, it has a replicated API dependency. Internally we've added a function that we unit test:
embedded-cluster/cmd/installer/cli/upgrade.go
Lines 630 to 633 in 041d8ec
} else { if err := opts.WithOnlineRequiredReleases(ctx, upgradeConfig.replicatedAPIClient); err != nil { return fmt.Errorf("failed to extract required releases from replidated API's pending release call: %w", err) }
This method is unit tested:
embedded-cluster/cmd/installer/cli/upgrade.go
Lines 627 to 629 in 041d8ec
if err := opts.WithAirgapRequiredReleases(upgradeConfig.airgapMetadata); err != nil { return fmt.Errorf("failed to extract required releases from airgap metadata: %w", err) }
So I'm trying to understand what we need to unit test, is it this logic?
embedded-cluster/cmd/installer/cli/upgrade.go
Lines 600 to 623 in 041d8ec
// Get current and target EC/K8s versions var currentECVersion string if currentInstallation.Spec.Config != nil { currentECVersion = currentInstallation.Spec.Config.Version } targetECVersion := versions.Version // Build validation options opts := validation.UpgradableOptions{ CurrentECVersion: currentECVersion, TargetECVersion: targetECVersion, License: upgradeConfig.license, } // Add current app version info if available if upgradeConfig.currentAppVersion != nil { opts.CurrentAppVersion = upgradeConfig.currentAppVersion.VersionLabel opts.CurrentAppSequence = upgradeConfig.currentAppVersion.ChannelSequence } // Add target app version info opts.TargetAppVersion = channelRelease.VersionLabel opts.TargetAppSequence = channelRelease.ChannelSequence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we only add a test for airgap?
|
|
||
| params := url.Values{} | ||
| params.Set("selectedChannelId", channelID) | ||
| params.Set("channelSequence", fmt.Sprintf("%d", currentSequence)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Mismatched Query Parameter Name in API Call
Query parameter name mismatch: the code sets "channelSequence" but the tests expect "currentSequence". Line 185 uses params.Set("channelSequence", ...) but the test assertions at client_test.go lines 417, etc. expect r.URL.Query().Get("currentSequence"). This will cause the API call to fail or return incorrect results because the server expects "currentSequence" as the parameter name.
cmd/installer/cli/upgrade.go
Outdated
| } | ||
| } else { | ||
| if err := opts.WithOnlineRequiredReleases(ctx, upgradeConfig.replicatedAPIClient); err != nil { | ||
| return fmt.Errorf("failed to extract required releases from replidated API's pending release call: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| var currentECVersion string | ||
| if currentInstallation.Spec.Config != nil { | ||
| currentECVersion = currentInstallation.Spec.Config.Version | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Invalid Version Validation When Config Is Nil
When currentInstallation.Spec.Config is nil, currentECVersion will be an empty string. This empty string is then passed to validateECVersionDowngrade() and validateK8sVersion() which call semver.NewVersion(""), causing a parsing error rather than a ValidationError. This breaks the error handling logic at lines 372-377 which expects ValidationError types for user-facing validation failures. The code should skip EC version and K8s version validation when currentECVersion is empty, or handle the empty case appropriately.
| // NewRequiredReleasesError creates a ValidationError indicating that intermediate | ||
| // required releases must be installed before upgrading to the target version | ||
| func NewRequiredReleasesError(requiredVersions []string, targetVersion string) *ValidationError { | ||
| return &ValidationError{ | ||
| Message: fmt.Sprintf("this upgrade requires installing intermediate version(s) first: %s. Please go through this upgrade path before upgrading to %s", | ||
| strings.Join(requiredVersions, ", "), targetVersion), | ||
| } | ||
| } | ||
|
|
||
| // NewAppVersionDowngradeError creates a ValidationError indicating that the target | ||
| // app version is older than the current version | ||
| func NewAppVersionDowngradeError(currentVersion, targetVersion string) *ValidationError { | ||
| return &ValidationError{ | ||
| Message: fmt.Sprintf("downgrade detected: cannot upgrade from app version %s to older version %s", currentVersion, targetVersion), | ||
| } | ||
| } | ||
|
|
||
| // NewECVersionDowngradeError creates a ValidationError indicating that the target | ||
| // Embedded Cluster version is older than the current version | ||
| func NewECVersionDowngradeError(currentVersion, targetVersion string) *ValidationError { | ||
| return &ValidationError{ | ||
| Message: fmt.Sprintf("downgrade detected: cannot upgrade from Embedded Cluster version %s to older version %s", currentVersion, targetVersion), | ||
| } | ||
| } | ||
|
|
||
| // NewK8sVersionSkipError creates a ValidationError indicating that the Kubernetes | ||
| // version upgrade skips a minor version, which is not supported by Kubernetes | ||
| func NewK8sVersionSkipError(currentVersion, targetVersion string) *ValidationError { | ||
| return &ValidationError{ | ||
| Message: fmt.Sprintf("Kubernetes version skip detected: cannot upgrade from k8s %s to %s. Kubernetes only supports upgrading by one minor version at a time", | ||
| currentVersion, targetVersion), | ||
| } | ||
| } | ||
|
|
||
| // NewK8sVersionDowngrade creates a ValidationError indicating that the Kubernetes | ||
| // version upgrade downgrades the kubernetes version used, which is not supported | ||
| func NewK8sVersionDowngrade(currentVersion, targetVersion string) *ValidationError { | ||
| return &ValidationError{ | ||
| Message: fmt.Sprintf("Kubernetes version downgrade detected: cannot downgrade from k8s %s to %s. Kubernetes downgrades are not supported", | ||
| currentVersion, targetVersion), | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ajp-io let me know what you think of the copy of these errors, there's some examples in the description of the PR on how these show up in the CLI.
|
Merging as failing test is unrelated. |
What this PR does / why we need it:
Adds verifications that the upgrade is possible.
Which issue(s) this PR fixes:
https://app.shortcut.com/replicated/story/130790/add-verification-that-release-is-deployable
Does this PR require a test?
Yes, added unit tests, we don't have a dryrun test for upgrades yet, I would imagine this would be one test we could add. Unsure if we should make it part of this PR or not.
There's still some manual tests I want to run though.Does this PR require a release note?
Does this PR require documentation?
NONE